#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Git Hooks 动态配置系统 - 任务执行器
负责执行各种类型的任务
"""

import os
import sys
import subprocess
import threading
import time
import logging
from typing import Dict, List, Any, Optional, Callable, Tuple
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
import importlib.util
import json
import re
from concurrent.futures import ThreadPoolExecutor, as_completed


class TaskStatus(Enum):
    """任务状态枚举"""
    PENDING = "pending"
    RUNNING = "running"
    SUCCESS = "success"
    FAILED = "failed"
    TIMEOUT = "timeout"
    SKIPPED = "skipped"


@dataclass
class TaskResult:
    """任务执行结果"""
    task_name: str
    status: TaskStatus
    output: str = ""
    error: str = ""
    duration: float = 0.0
    exit_code: int = 0
    metadata: Dict[str, Any] = None


class TaskExecutor:
    """任务执行器"""
    
    def __init__(self, config_dir: str = "config", plugins_dir: str = "plugins"):
        self.config_dir = Path(config_dir)
        self.plugins_dir = Path(plugins_dir)
        self.logger = logging.getLogger(__name__)
        self._plugins_cache = {}
        
    def execute_task(self, task_config: 'TaskConfig', context: Dict[str, Any] = None) -> TaskResult:
        """执行单个任务"""
        start_time = time.time()
        
        try:
            # 检查任务是否启用
            if not task_config.enabled:
                return TaskResult(
                    task_name=task_config.name,
                    status=TaskStatus.SKIPPED,
                    output="任务已禁用"
                )
            
            # 检查条件
            if not self._check_conditions(task_config.conditions, context or {}):
                return TaskResult(
                    task_name=task_config.name,
                    status=TaskStatus.SKIPPED,
                    output="条件不满足，跳过任务"
                )
            
            self.logger.info(f"开始执行任务: {task_config.name}")
            
            # 根据任务类型执行
            if task_config.type.value == "lint":
                result = self._execute_lint_task(task_config, context)
            elif task_config.type.value == "test":
                result = self._execute_test_task(task_config, context)
            elif task_config.type.value == "security":
                result = self._execute_security_task(task_config, context)
            elif task_config.type.value == "build":
                result = self._execute_build_task(task_config, context)
            elif task_config.type.value == "commit-msg-validator":
                result = self._execute_commit_msg_validator(task_config, context)
            else:
                result = self._execute_custom_task(task_config, context)
            
            result.duration = time.time() - start_time
            self.logger.info(f"任务执行完成: {task_config.name}, 状态: {result.status}, 耗时: {result.duration:.2f}s")
            
            return result
            
        except Exception as e:
            self.logger.error(f"任务执行异常: {task_config.name}, 错误: {e}")
            return TaskResult(
                task_name=task_config.name,
                status=TaskStatus.FAILED,
                error=str(e),
                duration=time.time() - start_time
            )
    
    def execute_tasks_parallel(self, tasks: List['TaskConfig'], context: Dict[str, Any] = None) -> List[TaskResult]:
        """并行执行多个任务"""
        results = []
        
        with ThreadPoolExecutor(max_workers=4) as executor:
            # 提交所有任务
            future_to_task = {
                executor.submit(self.execute_task, task, context): task
                for task in tasks
            }
            
            # 收集结果
            for future in as_completed(future_to_task):
                task = future_to_task[future]
                try:
                    result = future.result()
                    results.append(result)
                except Exception as e:
                    self.logger.error(f"并行任务执行失败: {task.name}, 错误: {e}")
                    results.append(TaskResult(
                        task_name=task.name,
                        status=TaskStatus.FAILED,
                        error=str(e)
                    ))
        
        return results
    
    def execute_tasks_sequential(self, tasks: List['TaskConfig'], context: Dict[str, Any] = None) -> List[TaskResult]:
        """顺序执行多个任务"""
        results = []
        
        for task in tasks:
            result = self.execute_task(task, context)
            results.append(result)
            
            # 如果任务失败且配置了快速失败，则停止执行
            if result.status == TaskStatus.FAILED:
                self.logger.warning(f"任务失败，停止后续任务执行: {task.name}")
                break
        
        return results
    
    def _check_conditions(self, conditions: List[Dict[str, Any]], context: Dict[str, Any]) -> bool:
        """检查任务执行条件"""
        for condition in conditions:
            condition_type = condition.get('type')
            
            if condition_type == 'file_pattern':
                if not self._check_file_pattern_condition(condition, context):
                    return False
            elif condition_type == 'file_exists':
                if not self._check_file_exists_condition(condition, context):
                    return False
            elif condition_type == 'branch_pattern':
                if not self._check_branch_pattern_condition(condition, context):
                    return False
            elif condition_type == 'commit_count':
                if not self._check_commit_count_condition(condition, context):
                    return False
            elif condition_type == 'time_range':
                if not self._check_time_range_condition(condition, context):
                    return False
        
        return True
    
    def _check_file_pattern_condition(self, condition: Dict[str, Any], context: Dict[str, Any]) -> bool:
        """检查文件模式条件"""
        pattern = condition.get('pattern', '')
        files = context.get('staged_files', [])
        
        if not files:
            return False
        
        import fnmatch
        for file_path in files:
            if fnmatch.fnmatch(file_path, pattern):
                return True
        
        return False
    
    def _check_file_exists_condition(self, condition: Dict[str, Any], context: Dict[str, Any]) -> bool:
        """检查文件存在条件"""
        file_path = condition.get('file', '')
        return os.path.exists(file_path)
    
    def _check_branch_pattern_condition(self, condition: Dict[str, Any], context: Dict[str, Any]) -> bool:
        """检查分支模式条件"""
        pattern = condition.get('pattern', '')
        current_branch = context.get('current_branch', '')
        
        if not current_branch:
            return False
        
        import fnmatch
        return fnmatch.fnmatch(current_branch, pattern)
    
    def _check_commit_count_condition(self, condition: Dict[str, Any], context: Dict[str, Any]) -> bool:
        """检查提交数量条件"""
        min_count = condition.get('min_count', 0)
        max_count = condition.get('max_count', float('inf'))
        commit_count = context.get('commit_count', 0)
        
        return min_count <= commit_count <= max_count
    
    def _check_time_range_condition(self, condition: Dict[str, Any], context: Dict[str, Any]) -> bool:
        """检查时间范围条件"""
        start_time = condition.get('start_time', '00:00')
        end_time = condition.get('end_time', '23:59')
        current_time = time.strftime('%H:%M')
        
        return start_time <= current_time <= end_time
    
    def _execute_lint_task(self, task_config: 'TaskConfig', context: Dict[str, Any]) -> TaskResult:
        """执行代码检查任务"""
        tools = task_config.config.get('tools', [])
        files = task_config.config.get('files', [])
        
        output_lines = []
        error_lines = []
        exit_code = 0
        
        for tool in tools:
            try:
                if tool == 'eslint':
                    result = self._run_eslint(files)
                elif tool == 'prettier':
                    result = self._run_prettier(files)
                else:
                    result = self._run_custom_lint_tool(tool, files)
                
                output_lines.extend(result['output'])
                if result['exit_code'] != 0:
                    exit_code = result['exit_code']
                    error_lines.extend(result['error'])
                    
            except Exception as e:
                error_lines.append(f"工具 {tool} 执行失败: {e}")
                exit_code = 1
        
        return TaskResult(
            task_name=task_config.name,
            status=TaskStatus.SUCCESS if exit_code == 0 else TaskStatus.FAILED,
            output='\n'.join(output_lines),
            error='\n'.join(error_lines),
            exit_code=exit_code
        )
    
    def _execute_test_task(self, task_config: 'TaskConfig', context: Dict[str, Any]) -> TaskResult:
        """执行测试任务"""
        command = task_config.config.get('command', 'npm test')
        timeout = task_config.config.get('timeout', 300)
        
        try:
            result = self._run_command(command, timeout=timeout)
            return TaskResult(
                task_name=task_config.name,
                status=TaskStatus.SUCCESS if result['exit_code'] == 0 else TaskStatus.FAILED,
                output=result['output'],
                error=result['error'],
                exit_code=result['exit_code']
            )
        except subprocess.TimeoutExpired:
            return TaskResult(
                task_name=task_config.name,
                status=TaskStatus.TIMEOUT,
                error=f"任务执行超时 ({timeout}s)"
            )
    
    def _execute_security_task(self, task_config: 'TaskConfig', context: Dict[str, Any]) -> TaskResult:
        """执行安全检查任务"""
        tools = task_config.config.get('tools', [])
        
        output_lines = []
        error_lines = []
        exit_code = 0
        
        for tool in tools:
            try:
                if tool == 'npm-audit':
                    result = self._run_npm_audit()
                elif tool == 'snyk':
                    result = self._run_snyk()
                else:
                    result = self._run_custom_security_tool(tool)
                
                output_lines.extend(result['output'])
                if result['exit_code'] != 0:
                    exit_code = result['exit_code']
                    error_lines.extend(result['error'])
                    
            except Exception as e:
                error_lines.append(f"安全工具 {tool} 执行失败: {e}")
                exit_code = 1
        
        return TaskResult(
            task_name=task_config.name,
            status=TaskStatus.SUCCESS if exit_code == 0 else TaskStatus.FAILED,
            output='\n'.join(output_lines),
            error='\n'.join(error_lines),
            exit_code=exit_code
        )
    
    def _execute_build_task(self, task_config: 'TaskConfig', context: Dict[str, Any]) -> TaskResult:
        """执行构建任务"""
        command = task_config.config.get('command', 'npm run build')
        timeout = task_config.config.get('timeout', 900)
        
        try:
            result = self._run_command(command, timeout=timeout)
            return TaskResult(
                task_name=task_config.name,
                status=TaskStatus.SUCCESS if result['exit_code'] == 0 else TaskStatus.FAILED,
                output=result['output'],
                error=result['error'],
                exit_code=result['exit_code']
            )
        except subprocess.TimeoutExpired:
            return TaskResult(
                task_name=task_config.name,
                status=TaskStatus.TIMEOUT,
                error=f"构建任务执行超时 ({timeout}s)"
            )
    
    def _execute_commit_msg_validator(self, task_config: 'TaskConfig', context: Dict[str, Any]) -> TaskResult:
        """执行提交信息验证任务"""
        commit_msg = context.get('commit_msg', '')
        pattern = task_config.config.get('pattern', '')
        min_length = task_config.config.get('min_length', 0)
        max_length = task_config.config.get('max_length', float('inf'))
        
        # 新增配置项
        require_issue = task_config.config.get('require_issue', False)
        require_signed_off = task_config.config.get('require_signed_off', False)
        allowed_email_domains = task_config.config.get('allowed_email_domains', [])
        
        errors = []
        
        # 检查长度
        if len(commit_msg) < min_length:
            errors.append(f"提交信息太短，最少需要 {min_length} 个字符")
        
        if len(commit_msg) > max_length:
            errors.append(f"提交信息太长，最多允许 {max_length} 个字符")
        
        # 检查格式
        if pattern:
            if not re.match(pattern, commit_msg):
                errors.append(f"提交信息格式不符合要求: {pattern}")
        
        # 检查是否包含 Issue
        if require_issue:
            if not self._check_issue_reference(commit_msg):
                errors.append("提交信息必须包含 Issue 引用（如：Issue: #123 或 Fixes #123）")
        
        # 检查 Signed-off-by
        if require_signed_off:
            signed_off_error = self._check_signed_off_by(commit_msg, allowed_email_domains)
            if signed_off_error:
                errors.append(signed_off_error)
        
        return TaskResult(
            task_name=task_config.name,
            status=TaskStatus.SUCCESS if not errors else TaskStatus.FAILED,
            output="提交信息验证通过" if not errors else "",
            error='\n'.join(errors) if errors else "",
            exit_code=0 if not errors else 1
        )
    
    def _check_issue_reference(self, commit_msg: str) -> bool:
        """检查提交信息是否包含 Issue 引用"""
        # 支持多种 Issue 引用格式
        issue_patterns = [
            r'Issue:\s*#?\d+',           # Issue: #123 或 Issue: 123
            r'Issues?:\s*#?\d+',         # Issue: #123 或 Issues: #123
            r'Fixes?\s+#?\d+',           # Fixes #123 或 Fix #123
            r'Closes?\s+#?\d+',          # Closes #123 或 Close #123
            r'Resolves?\s+#?\d+',        # Resolves #123 或 Resolve #123
            r'#\d+',                     # #123
        ]
        
        for pattern in issue_patterns:
            if re.search(pattern, commit_msg, re.IGNORECASE):
                return True
        return False
    
    def _check_signed_off_by(self, commit_msg: str, allowed_domains: List[str]) -> str:
        """检查 Signed-off-by 行"""
        # 查找 Signed-off-by 行
        signed_off_pattern = r'Signed-off-by:\s*(.+?)\s*<(.+?)>'
        matches = re.findall(signed_off_pattern, commit_msg, re.IGNORECASE | re.MULTILINE)
        
        if not matches:
            return "提交信息必须包含 Signed-off-by 行（格式：Signed-off-by: Name <email@domain.com>）"
        
        # 检查邮箱域名
        if allowed_domains:
            for name, email in matches:
                email = email.strip().lower()
                domain_valid = False
                
                for domain in allowed_domains:
                    if email.endswith('@' + domain.lower()):
                        domain_valid = True
                        break
                
                if not domain_valid:
                    allowed_domains_str = ', '.join(allowed_domains)
                    return f"Signed-off-by 邮箱域名不符合要求。当前邮箱: {email}，允许的域名: {allowed_domains_str}"
        
        return ""  # 验证通过
    
    def _execute_custom_task(self, task_config: 'TaskConfig', context: Dict[str, Any]) -> TaskResult:
        """执行自定义任务"""
        plugin_name = task_config.config.get('plugin', '')
        if not plugin_name:
            return TaskResult(
                task_name=task_config.name,
                status=TaskStatus.FAILED,
                error="未指定自定义插件"
            )
        
        try:
            plugin = self._load_plugin(plugin_name)
            if not plugin:
                return TaskResult(
                    task_name=task_config.name,
                    status=TaskStatus.FAILED,
                    error=f"无法加载插件: {plugin_name}"
                )
            
            result = plugin.execute(task_config.config, context)
            return TaskResult(
                task_name=task_config.name,
                status=TaskStatus.SUCCESS if result.get('success', False) else TaskStatus.FAILED,
                output=result.get('output', ''),
                error=result.get('error', ''),
                exit_code=result.get('exit_code', 0)
            )
            
        except Exception as e:
            return TaskResult(
                task_name=task_config.name,
                status=TaskStatus.FAILED,
                error=f"自定义任务执行失败: {e}"
            )
    
    def _run_command(self, command: str, timeout: int = 300, cwd: str = None) -> Dict[str, Any]:
        """运行命令"""
        try:
            result = subprocess.run(
                command,
                shell=True,
                capture_output=True,
                text=True,
                timeout=timeout,
                cwd=cwd or os.getcwd()
            )
            
            return {
                'output': result.stdout,
                'error': result.stderr,
                'exit_code': result.returncode
            }
        except subprocess.TimeoutExpired:
            raise
    
    def _run_eslint(self, files: List[str]) -> Dict[str, Any]:
        """运行 ESLint"""
        if not files:
            return {'output': [], 'error': [], 'exit_code': 0}
        
        command = f"npx eslint {' '.join(files)}"
        result = self._run_command(command)
        
        return {
            'output': result['output'].split('\n') if result['output'] else [],
            'error': result['error'].split('\n') if result['error'] else [],
            'exit_code': result['exit_code']
        }
    
    def _run_prettier(self, files: List[str]) -> Dict[str, Any]:
        """运行 Prettier"""
        if not files:
            return {'output': [], 'error': [], 'exit_code': 0}
        
        command = f"npx prettier --check {' '.join(files)}"
        result = self._run_command(command)
        
        return {
            'output': result['output'].split('\n') if result['output'] else [],
            'error': result['error'].split('\n') if result['error'] else [],
            'exit_code': result['exit_code']
        }
    
    def _run_npm_audit(self) -> Dict[str, Any]:
        """运行 npm audit"""
        command = "npm audit"
        result = self._run_command(command)
        
        return {
            'output': result['output'].split('\n') if result['output'] else [],
            'error': result['error'].split('\n') if result['error'] else [],
            'exit_code': result['exit_code']
        }
    
    def _run_snyk(self) -> Dict[str, Any]:
        """运行 Snyk"""
        command = "npx snyk test"
        result = self._run_command(command)
        
        return {
            'output': result['output'].split('\n') if result['output'] else [],
            'error': result['error'].split('\n') if result['error'] else [],
            'exit_code': result['exit_code']
        }
    
    def _load_plugin(self, plugin_name: str):
        """加载插件"""
        if plugin_name in self._plugins_cache:
            return self._plugins_cache[plugin_name]
        
        plugin_path = self.plugins_dir / f"{plugin_name}.py"
        if not plugin_path.exists():
            return None
        
        try:
            spec = importlib.util.spec_from_file_location(plugin_name, plugin_path)
            module = importlib.util.module_from_spec(spec)
            spec.loader.exec_module(module)
            
            self._plugins_cache[plugin_name] = module
            return module
            
        except Exception as e:
            self.logger.error(f"加载插件失败: {plugin_name}, 错误: {e}")
            return None


if __name__ == "__main__":
    # 测试任务执行器
    from .config_parser import TaskConfig, TaskType
    
    executor = TaskExecutor()
    
    # 创建测试任务
    test_task = TaskConfig(
        name="测试任务",
        type=TaskType.LINT,
        config={
            'tools': ['eslint'],
            'files': ['*.js']
        }
    )
    
    # 执行任务
    context = {
        'staged_files': ['test.js'],
        'current_branch': 'main'
    }
    
    result = executor.execute_task(test_task, context)
    print(f"任务执行结果: {result}")
